﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tomahawk.Runtime.Resources;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Collections;
using Microsoft.Xna.Framework.Content;

namespace Tomahawk.Runtime.Rendering.Materials
{
    #region SAS Effect Exceptions
    public class SASInvalidEffectException : Exception
    {
    }
    #endregion

    public class SASEffect
    {
        #region SASEffectParametersCollection
        public class SASEffectParametersCollection : IEnumerable
        {
            protected SASEffect effect;

            public SASEffectParametersCollection(SASEffect effect)
            {
                this.effect = effect;
            }
            public SASParameter this[string index]
            {
                get
                {
                    SASParameter parameter = null;
                    effect.parameterTable.TryGetValue(index, out parameter);
                    return parameter;
                }
            }
            public T Get<T>(string index) where T : SASParameter
            {
                SASParameter parameter = this[index];
                return parameter != null ? (parameter as T) : null;
            }

            #region IEnumerable Members

            public IEnumerator GetEnumerator()
            {
                return effect.parameterTable.GetEnumerator();
            }

            #endregion
        }
        #endregion

        #region Render Group
        public enum RenderGroup
        {
            None = -1,
            Color,
            Canvas,
            Depth,
            Custom_0,
            Custom_1,
            Custom_2,
            Custom_3,
            Custom_4,
            Custom_5,
            Custom_6,
            Custom_7,
            Custom_8,
            Custom_9,
            Custom_10,
            Custom_11,
            Custom_12,
            RenderGroupCount
        }

        [Flags]
        public enum RenderGroupFlags
        {
            None = 0,
            Color = 1 << RenderGroup.Color,
            Canvas = 1 << RenderGroup.Canvas,
            Depth = 1 << RenderGroup.Depth,
            Custom_0 = 1 << RenderGroup.Custom_0,
            Custom_1 = 1 << RenderGroup.Custom_1,
            Custom_2 = 1 << RenderGroup.Custom_2,
            Custom_3 = 1 << RenderGroup.Custom_3,
            Custom_4 = 1 << RenderGroup.Custom_4,
            Custom_5 = 1 << RenderGroup.Custom_5,
            Custom_6 = 1 << RenderGroup.Custom_6,
            Custom_7 = 1 << RenderGroup.Custom_7,
            Custom_8 = 1 << RenderGroup.Custom_8,
            Custom_9 = 1 << RenderGroup.Custom_9,
            Custom_10 = 1 << RenderGroup.Custom_10,
            Custom_11 = 1 << RenderGroup.Custom_11,
            Custom_12 = 1 << RenderGroup.Custom_12,
        }
        public static RenderGroup currentRenderGroup = RenderGroup.Color;
        #endregion

        #region Events
        public delegate bool DrawGeometryDelegate(string script, SASEffect.RenderGroup group, GraphicsDevice device);
        public event DrawGeometryDelegate OnDrawGeometry = null;
        public bool RaiseDrawGeometry(string script, SASEffect.RenderGroup group, GraphicsDevice device) { return OnDrawGeometry(script, group, device); }

        public delegate bool DrawCanvasDelegate(string script, GraphicsDevice device);
        public event DrawCanvasDelegate OnDrawCanvas = null;
        public bool RaiseDrawCanvas(string script, GraphicsDevice device) { return OnDrawCanvas(script, device); }
        #endregion

        #region Properties
        protected string effectFile = null;
        internal RenderTarget renderSource = null;
        internal RenderTarget renderTarget = null;

        protected Effect effect;
        public Effect Effect { get { return effect; } }

        protected SASTechnique[] preferredTechniques = new SASTechnique[(int) RenderGroup.RenderGroupCount];
        protected Dictionary<string, SASTechnique> techniqueTable = null;
        public Dictionary<string, SASTechnique> TechniqueTable { get { return techniqueTable; } }

        public SASTechnique CurrentTechnique
        {
            get
            {
                return GetPreferredTechnique(currentRenderGroup);
            }
        }

        protected Dictionary<string, SASParameter> parameterTable = null;
        protected SASEffectParametersCollection parameters = null;
        public SASEffectParametersCollection Parameters { get { return parameters; } }

        private List<SASParameter> updatePerFrameParameterCache = null;
        private List<SASParameter> updatePerObjectParameterCache = null;
        #endregion

        public SASEffect()
        { 
        }

        public SASEffect(string effectFile, ContentManager content)
        {
            Initialize(effectFile, content);
        }

        #region Initialization
        public virtual bool Initialize(string effectFile, ContentManager content)
        {
            if (this.effectFile != null)
            {
                return false;
            }

            this.effectFile = effectFile;
            this.techniqueTable = new Dictionary<string, SASTechnique>(StringComparer.OrdinalIgnoreCase);
            this.parameterTable = new Dictionary<string, SASParameter>(StringComparer.OrdinalIgnoreCase);
            this.parameters = new SASEffectParametersCollection(this);

            this.updatePerFrameParameterCache = new List<SASParameter>();
            this.updatePerObjectParameterCache = new List<SASParameter>();

            lock (Engine.ResourceLock)
            {
                Effect resource = content.Load<Effect>(effectFile);
                effect = resource.Clone(Engine.Instance.GraphicsDevice);

                foreach (EffectParameter parameter in effect.Parameters)
                {
                    SASParameter sasParameter = SASParameterBuilder.Build(parameter);
                    parameterTable.Add(sasParameter.Name, sasParameter);

                    switch (sasParameter.UpdateMode)
                    {
                        case SASParameter.UpdateType.UpdatePerFrame: updatePerFrameParameterCache.Add(sasParameter); break;
                        case SASParameter.UpdateType.UpdatePerObject: updatePerObjectParameterCache.Add(sasParameter); break;
                    }
                }

                foreach (EffectTechnique technique in effect.Techniques)
                {
                    SASTechnique sasTechnique = new SASTechnique();
                    sasTechnique.Initialize(this, technique);
                    techniqueTable.Add(sasTechnique.Name, sasTechnique);
                }

                bool result = false;
                SASScriptParameter script = Parameters.Get<SASScriptParameter>("SCRIPT");

                if (script != null)
                {
                    result = script.Execute(this);

                    SASTechnique currentTechnique = GetPreferredTechnique(RenderGroup.Color);
                    if (currentTechnique == null)
                    {
                        techniqueTable.TryGetValue(effect.CurrentTechnique.Name, out currentTechnique);
                        preferredTechniques[(int)RenderGroup.Color] = currentTechnique;
                    }
                }
                else
                {
                    foreach (EffectTechnique technique in effect.Techniques)
                    {
                        SASTechnique sasTechnique = null;
                        if (techniqueTable.TryGetValue(technique.Name, out sasTechnique))
                        {
                            if (sasTechnique.Valid)
                            {
                                preferredTechniques[(int)RenderGroup.Color] = sasTechnique;
                                break;
                            }
                        }
                    }
                }

                return result;
            }
        }
        #endregion

        #region Techniques
        private SASTechnique GetFirstValidTechnique(string script)
        {
            string[] techniques = script.Split(new char[] { ':' });
            foreach (string technique in techniques)
            {
                SASTechnique sasTechnique = null;
                if (techniqueTable.TryGetValue(technique, out sasTechnique))
                {
                    if (sasTechnique.Valid)
                    {
                        return sasTechnique;
                    }
                }
            }

            return null;
        }

        public bool IsTechniqueAvailable(RenderGroup group)
        {
            SASTechnique value = null;
            value = preferredTechniques[(int)group];

            return (value != null);
        }

        public SASTechnique GetPreferredTechnique(RenderGroup group)
        {
            SASTechnique value = null;
            value = preferredTechniques[(int)group];

            if (value == null)
            {
                value = preferredTechniques[(int)RenderGroup.Color];
            }

            return value;
        }

        public void SetPreferredTechnique(RenderGroup group, string technique)
        {
            SASTechnique sasTechnique = null;
            if (techniqueTable.TryGetValue(technique, out sasTechnique))
            {
                if (sasTechnique.Valid)
                {
                    preferredTechniques[(int)group] = sasTechnique;
                }
            }
        }

        public bool SelectTechniques(string script)
        {
            string[] tokens = script.Split(new char[] { '?' });

            string type = tokens[0];
            string value = tokens[1];

            if ((string.Compare(type, "TECHNIQUE", true) == 0) ||
                (string.Compare(type, "COLOR", true) == 0))
            {
                preferredTechniques[(int) RenderGroup.Color] = GetFirstValidTechnique(value);
            }
            else if (string.Compare(type, "DEPTH", true) == 0)
            {
                preferredTechniques[(int)RenderGroup.Depth] = GetFirstValidTechnique(value);
            }
            else
            {
                RenderGroup group = RenderGroup.Color;
                if (Engine.Instance.RenderManager.GenerateRenderGroupId != null)
                {
                    group = Engine.Instance.RenderManager.GenerateRenderGroupId(type);
                }

                if (group != RenderGroup.None)
                {
                    preferredTechniques[(int) group] = GetFirstValidTechnique(value);
                }
            }

            SASTechnique currentTechnique = GetPreferredTechnique(RenderGroup.Color);
            if (currentTechnique != null)
            {
                effect.CurrentTechnique = currentTechnique.Technique;
            }

            return (effect.CurrentTechnique != null);
        }
        #endregion

        #region Update parameters
        public bool Update(SASRenderEnvironment environment, SASParameter.UpdateType type)
        {
            switch (type)
            {
                case SASParameter.UpdateType.UpdatePerFrame:
                    {
                        int parameterCount = updatePerFrameParameterCache.Count;
                        for (int idx = 0; idx < parameterCount; ++idx)
                        {
                            updatePerFrameParameterCache[idx].Update(environment);
                        }
                    } break;

                case SASParameter.UpdateType.UpdatePerObject:
                    {
                        int parameterCount = updatePerObjectParameterCache.Count;
                        for (int idx = 0; idx < parameterCount; ++idx)
                        {
                            updatePerObjectParameterCache[idx].Update(environment);
                        }
                    } break;
            }
            return true;
        }
        #endregion

        #region Surfaces
        internal virtual bool LoadSurfaces(GraphicsDevice gd)
        {
            return false;
        }

        internal virtual bool UnloadSurfaces(GraphicsDevice gd)
        {
            return false;
        }

        internal virtual bool ActivateDepthStencilBuffer(string name, GraphicsDevice gd)
        {
            return false;
        }

        internal virtual bool ActivateRenderTarget(int index, string name, GraphicsDevice gd)
        {
            return false;
        }
        #endregion

        #region Rendering
        public static void Begin(VertexBuffer vertices, IndexBuffer indices, int offset, VertexDeclaration declaration, GraphicsDevice device)
        {
            device.Indices = indices;
            device.VertexDeclaration = declaration;
            device.Vertices[0].SetSource(vertices, offset, declaration.GetVertexStrideSize(0));
        }

        public static void End()
        {
        }

        public virtual bool DrawQuad(GraphicsDevice gd)
        {
            return false;
        }

        public virtual bool DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount, GraphicsDevice device)
        {
            bool result = true;
            SASTechnique selectedTechnique = GetPreferredTechnique(currentRenderGroup);

            if (selectedTechnique != null)
            {
                if (effect.CurrentTechnique != selectedTechnique.Technique)
                {
                    effect.CurrentTechnique = selectedTechnique.Technique;
                }

                effect.Begin();

                SASScript.Context context = SASScript.Context.EmptyContext;
                context.sasEnvironment = Engine.Instance.RenderManager.Environment;
                context.sasEffect = this;
                context.sasTechnique = selectedTechnique;
                context.sasPass = null;
                context.primitiveType = primitiveType;
                context.baseVertex = baseVertex;
                context.numVertices = numVertices;
                context.indexedVertices = true;
                context.startIndex = startIndex;
                context.primitiveCount = primitiveCount;
                context.device = device;

                result = selectedTechnique.DrawMesh(ref context);

                effect.End();
            }

            return result;
        }

        public virtual bool DrawMesh(ModelMesh mesh, ModelMeshPart part, GraphicsDevice device)
        {
            bool result = true;
            SASTechnique selectedTechnique = GetPreferredTechnique(currentRenderGroup);

            if (selectedTechnique != null)
            {
                if (effect.CurrentTechnique != selectedTechnique.Technique)
                {
                    effect.CurrentTechnique = selectedTechnique.Technique;
                }

                effect.Begin();

                SASScript.Context context = SASScript.Context.EmptyContext;
                context.sasEnvironment = Engine.Instance.RenderManager.Environment;
                context.sasEffect = this;
                context.sasTechnique = selectedTechnique;
                context.sasPass = null;
                context.primitiveType = PrimitiveType.TriangleList;
                context.baseVertex = part != null ? part.BaseVertex : 0;
                context.numVertices = part != null ? part.NumVertices : 0;
                context.indexedVertices = true;
                context.startIndex = part != null ? part.StartIndex : 0;
                context.primitiveCount = part != null ? part.PrimitiveCount : 0;
                context.device = device;

                result = selectedTechnique.DrawMesh(ref context);

                effect.End();
            }

            return result;
        }

        public virtual bool DrawScene(GraphicsDevice device)
        {
            return false;
        }

        public virtual bool DrawExternal(string script, GraphicsDevice device)
        {
            return false;
        }

        #endregion

        #region Rendering Commands
        public virtual bool BeginCommands(GraphicsDevice device)
        {
            bool result = false;

            SASTechnique selectedTechnique = GetPreferredTechnique(currentRenderGroup);

            if (selectedTechnique != null)
            {
                if (effect.CurrentTechnique != selectedTechnique.Technique)
                {
                    effect.CurrentTechnique = selectedTechnique.Technique;
                }

                effect.Begin();
                result = true;
            }

            return result;
        }

        public virtual bool EndCommands(GraphicsDevice device)
        {
            bool result = false;
            SASTechnique selectedTechnique = GetPreferredTechnique(currentRenderGroup);

            if (selectedTechnique != null)
            {
                effect.End();
                result = true;
            }

            return result;
        }
        #endregion
    }
}
